﻿using System;
using System.Collections.Generic;

// This is the class, that knows about all other classes. Instances of almost everything are kept here. Most of the game logic is also here.
public class Engine
{
    protected IRenderer renderer;
    protected IUserInterface input;
    protected GameBoard gameBoard;
    protected Player player;
    protected bool gameOver;
    protected TimeSpan oldTime;

    public Engine(IRenderer renderer, IUserInterface input, GameBoard gameBoard, Player player)
    {
        this.renderer = renderer;
        this.input = input;
        this.gameBoard = gameBoard;
        this.player = player;
        this.gameOver = false;
        this.oldTime = Timer.GetElapsedTime();
    }

    public virtual void MovePlayerLeft()
    {
        if (this.player.CoordsNew.Col > 0)
        {
            this.player.MoveLeft();
            this.renderer.RenderPlayer(this.player, this.gameBoard.RowOffset, this.gameBoard.ColOffset);
        }
    }

    public virtual void MovePlayerUp()
    {
        if (this.player.CoordsNew.Row > 0)
        {
            this.player.MoveUp();
            this.renderer.RenderPlayer(this.player, this.gameBoard.RowOffset, this.gameBoard.ColOffset);
        }
    }

    public virtual void MovePlayerRight()
    {
        if (this.player.CoordsNew.Col < this.gameBoard.Cols - 1)
        {
            this.player.MoveRight();
            this.renderer.RenderPlayer(this.player, this.gameBoard.RowOffset, this.gameBoard.ColOffset);
        }
    }

    public virtual void MovePlayerDown()
    {
        if (this.player.CoordsNew.Row < this.gameBoard.Rows - 1)
        {
            this.player.MoveDown();
            this.renderer.RenderPlayer(this.player, this.gameBoard.RowOffset, this.gameBoard.ColOffset);
        }
    }

    public virtual void OpenSquare()
    {
        if (this.gameBoard[this.player.CoordsNew] is EmptySquare)
        {
            OpenAllAdjacentNonMineSquares(this.player.CoordsNew);
        }
        else if (this.gameBoard[this.player.CoordsNew] is NumberSquare)
        {
            this.gameBoard[this.player.CoordsNew].IsOpened = true;
        }
        else if (this.gameBoard[this.player.CoordsNew] is MineSquare)
        {
            this.renderer.RenderGameOver(gameBoard);
            this.gameOver = true;
            return;
        }
        else
        {
            throw new GameObjectNotASquareException();
        }

        this.renderer.RenderBoard(gameBoard);
    }

    public virtual void MarkAsMine()
    {
        this.gameBoard[this.player.CoordsNew].IsMarkedAsMine = !this.gameBoard[this.player.CoordsNew].IsMarkedAsMine;
        this.renderer.RenderBoard(gameBoard);
    }

    private void OpenAllAdjacentNonMineSquares(Coords2D clickedSquare)
    {
        Queue<Coords2D> queueBFS = new Queue<Coords2D>();

        queueBFS.Enqueue(clickedSquare);

        while (queueBFS.Count != 0)
        {
            Coords2D middleSquare = queueBFS.Dequeue();

            if (this.gameBoard[middleSquare] is EmptySquare && !this.gameBoard[middleSquare].IsOpened)
            {
                this.gameBoard[middleSquare].IsOpened = true;

                int startRow = Math.Max(0, middleSquare.Row - 1);
                int endRow = Math.Min(this.gameBoard.Rows - 1, middleSquare.Row + 1);

                int startCol = Math.Max(0, middleSquare.Col - 1);
                int endCol = Math.Min(this.gameBoard.Cols - 1, middleSquare.Col + 1);

                for (int row = startRow; row <= endRow; row++)
                {
                    for (int col = startCol; col <= endCol; col++)
                    {
                        Coords2D currentSquare = new Coords2D(row, col);

                        queueBFS.Enqueue(currentSquare);
                    }
                }
            }
            else
            {
                this.gameBoard[middleSquare].IsOpened = true;
            }
        }
    }

    public void UpdateGameTime()
    {
        TimeSpan newTime = Timer.GetElapsedTime();

        if (this.oldTime < newTime)
        {
            this.oldTime = newTime;
            this.renderer.RenderPlayTime(this.gameBoard.TimerLocation, newTime);
        }
    }

    public virtual void Run()
    {
        this.renderer.RenderBoard(this.gameBoard);
        this.renderer.RenderPlayerInitially(this.player, this.gameBoard.RowOffset, this.gameBoard.ColOffset);

        while (!this.gameOver)
        {
            UpdateGameTime();

            this.input.ProcessInput();
        }
    }
}
